package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"io"
)

var (
	AES_KEY = []byte("Gricks CBC Encry")
)

const (
	Session_Size     = 16
	Offset_Uid       = 0
	Offset_Gid       = 4
	Offset_Expire    = 8
	Offset_Timestamp = 12
)

type Session struct {
	Uid       uint32
	Gid       uint32
	Expire    uint32
	Timestamp uint32
}

func (self *Session) encode() []byte {
	buffer := [Session_Size]byte{}
	binary.LittleEndian.PutUint32(buffer[Offset_Uid:], self.Uid)
	binary.LittleEndian.PutUint32(buffer[Offset_Gid:], self.Gid)
	binary.LittleEndian.PutUint32(buffer[Offset_Expire:], self.Expire)
	binary.LittleEndian.PutUint32(buffer[Offset_Timestamp:], self.Timestamp)
	return buffer[:]
}

func (self *Session) decode(buffer []byte) {
	self.Uid = binary.LittleEndian.Uint32(buffer[Offset_Uid:])
	self.Gid = binary.LittleEndian.Uint32(buffer[Offset_Gid:])
	self.Expire = binary.LittleEndian.Uint32(buffer[Offset_Expire:])
	self.Timestamp = binary.LittleEndian.Uint32(buffer[Offset_Timestamp:])
}

func (self *Session) Encryp() ([]byte, error) {
	// CBC mode works on blocks so plaintexts may need to be padded to the
	// next whole block. For an example of such padding, see
	// https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
	// assume that the plaintext is already of the correct length.
	plaintext := self.encode()
	if len(plaintext)%aes.BlockSize != 0 {
		return nil, errors.New("invalid plaintext, not a multiple of the block size")
	}
	block, err := aes.NewCipher(AES_KEY)
	if err != nil {
		return nil, err
	}

	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

	return ciphertext, nil
}

func (self *Session) Decryp(ciphertext []byte) error {
	block, err := aes.NewCipher(AES_KEY)
	if err != nil {
		return err
	}

	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	if len(ciphertext) < aes.BlockSize {
		return errors.New("invalid ciphertext, too short")
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	// CBC mode always works in whole blocks.
	if len(ciphertext)%aes.BlockSize != 0 {
		return errors.New("invalid ciphertext, not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(ciphertext, ciphertext)

	self.decode(ciphertext)
	return nil
}
